﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//#include <windows.h>
//#include <time.h>

#include "common.h"

int row = 11, col = 11, mine_num = 10; // 行(-2)、列(-2)、雷数
char mine_flag = '@'; // 雷标记
char self_flag = 'X'; // 玩家自定义标记符
char shade_flag = '*'; // 未挖开标记
char is_sign = 'Y'; // 是否需要标记功能

extern void clean_cache(); // 清理缓存区函数，在三子棋游戏中，代码如下：
/*void clean_cache()
{ // 清理缓存区的函数，用于逐一读取缓冲区数据，遇到回车键停止（字符'\n'）
	while (getchar() != '\n')
		continue;
}*/

void sweep_menu()
{ // 扫雷游戏菜单函数
	printf("####################\n");
	printf("#### 扫雷游戏 ####\n");
	printf("### 1.开始游戏 ###\n");
	printf("### 0.退出游戏 ###\n");
	printf("## 2.自定义游戏 ##\n");
	printf("####################\n");
}

void self_define()
{
	while (1)
	{
	error1:
		printf("请输入行数（实际玩游戏，行数会少2行）实际最大可玩100行哦：");
		int row_custom = scanf("%d", &row);
		clean_cache(); // 清理缓存区
		if (row < 4 || row > 102)
		{
			printf("行数输入错误，请重新输入！\n");
			goto error1;
		}
	error2:
		printf("请输入列数（实际玩游戏，列数会少2列）实际最大可玩100列哦：");
		int col_custom = scanf("%d", &col);
		clean_cache();
		if (col < 4 || col > 102)
		{
			printf("列数输入错误，请重新输入！\n");
			goto error2;
		}
	error3:
		printf("请输入雷数（实际游戏的总雷数）：");
		int mine_custom = scanf("%d", &mine_num);
		clean_cache();
		if (mine_num < 1 || mine_num >(row - 2) * (col - 2) - 1)
		{
			printf("雷数输入错误，请重新输入！\n");
			goto error3;
		}
		do
		{
			printf(
				"是否需要标记功能(Y/N)，选择Y可以标记雷位置(每次都需选择标记与否)：");
			int is_sign_custom = scanf("%c", &is_sign);
			clean_cache();
			if (is_sign != 'Y' && is_sign != 'N'
				&& is_sign != 'y' && is_sign != 'n')
			{
				printf("输入错误，请重新输入！\n");
				continue;
			}
			else if (is_sign == 'Y' || is_sign == 'y')
			{
			error4:
				printf("请输入自己的标记符(不能是 %c )：",
					mine_flag);
				int sign_custom = scanf("%c", &self_flag);
				clean_cache();
				if (mine_flag == self_flag)
				{
					printf("标记符不能是'@'，请重新输入！\n");
					goto error4;
				}
				else if (1 != sign_custom)
				{
					printf("标记符只能是一个字符哦，请重新输入！\n");
					goto error4;
				}
			}
			break;
		} while (1);
		if (row < 1 || col < 1 ||
			mine_num < 1 || mine_num > row * col - 1)
		{
			printf("输入错误，请重新输入！\n");
			continue;
		}
		else
			break;
	}
}

// 创建 m*n 的二维数组，并返回指向该数组的指针
char** create_2d_array(int m, int n)
{ // 创建二维数组函数(由FittenCode生成)
	char** array = (char**)malloc(m * sizeof(char*)); // 为行分配内存
	if (NULL == array)
		return NULL; // 内存分配失败

	int i, j;
	for (i = 0; i < m; i++)
	{
		array[i] = (char*)malloc(n * sizeof(char)); // 为每列分配内存
		if (NULL == array[i])
		{
			// 如果分配失败，需要释放之前分配的内存
			for (j = 0; j < i; j++)
				free(array[j]);
			free(array);
			return NULL; // 内存分配失败
		}
	}

	return array; // 返回指向二维数组的指针
}

//void free_2d_array(char** array, int m)
//{ // 释放二维数组内存的函数
//	for (int i = 0; i < m; i++)
//		free(array[i]); // 释放每一行
//	free(array); // 释放行指针数组
//}

void init_board(char** board)
{ // 初始化扫雷游戏棋盘
	int i, j, k;
	for (i = 0; i < row; ++i)
	{
		for (j = 0; j < col; ++j)
			board[i][j] = '0'; // 初始化为空格
	}
	for (k = 0; k < mine_num; ++k)
	{
		i = (rand() % (row - 2)) + 1; // 随机生成行(1 ~ row - 1)
		j = (rand() % (col - 2)) + 1; // 随机生成列(1 ~ col - 1)
		if ('0' == board[i][j])
			board[i][j] = mine_flag; // 标记为雷
		else
			--k; // 重新生成坐标
	}
}

void init_shade_board(char** shade_board)
{
	int i, j;
	for (i = 0; i < row; ++i)
	{
		for (j = 0; j < col; ++j)
			shade_board[i][j] = shade_flag; // 初始化遮罩标记
	}
}

void print_board(char** shade_board)
{ // 打印扫雷游戏棋盘函数
	int i, j;
	for (i = 0; i < row - 1; ++i)
	{
		for (j = 0; j < col - 1; ++j)
		{
			if (i != 0 && j != 0)
				if (i < 10 && j < 10)
					printf("%c ", shade_board[i][j]);
				else if (i < 10 && j >= 10)
					printf("%c  ", shade_board[i][j]);
				else if (i >= 10 && j < 10)
					printf("%c ", shade_board[i][j]);
				else
					printf("%c  ", shade_board[i][j]);
			else
			{
				if (0 == i + j)
					printf(" ");
				else
					printf("%d ", i + j);
				if (0 == i && 0 == j)
					printf(" "); // 打印空格
				else if (0 == i && col - 2 == j)
					printf("\n"); // 换行
				if (0 == j)
					if (i < 10)
						printf("   ");
					else
						printf("  ");
			}
		}
		printf("\n");
	}
	printf("\n");
}

int chack_mine_count(char** board, char** shade_board,
	int row_input, int col_input)
{ // 检查周围有几颗雷，改变遮罩标记为雷数，并返回雷数
	char mine_count = '0';
	if (mine_flag == board[row_input][col_input])
		return -1; // 输入位置有雷，返回 -1 
	else if (row_input >= 1 && row_input <= row - 2 &&
		col_input >= 1 && col_input <= col - 2)
	{	// 输入位置在棋盘内
		int i, j;
		for (i = -1; i <= 1; ++i)
			for (j = -1; j <= 1; ++j)
				if (mine_flag == board[row_input + i][col_input + j])
					++mine_count; // 统计周围雷数
	}
	else
		return -2; // 输入位置不在棋盘内，返回 -2
	shade_board[row_input][col_input] = mine_count; // 标记周围雷数
	if ('0' == mine_count)
	{
		int i, j;
		for (i = -1; i <= 1; ++i)
			for (j = -1; j <= 1; ++j)
				if (shade_flag == shade_board[row_input + i][col_input + j])
					chack_mine_count(board, shade_board,
						row_input + i, col_input + j);
	}
	return mine_count - '0'; // 返回周围雷数（字符本质上是一个整数）
}

int residue_shade_count(char** shade_board)
{ // 剩余遮罩数与标记数计算函数
	int residue = 0;
	int i, j;
	for (i = 1; i < row - 1; ++i)
		for (j = 1; j < col - 1; ++j)
			if (shade_flag == shade_board[i][j] ||
				self_flag == shade_board[i][j])
				++residue; // 统计遮罩数、标记数
	return residue; // 返回遮罩数与标记数的和
}

void player_game(int* row_input, int* col_input)
{ // 扫雷游戏玩家扫雷函数，初始化玩家输入的坐标
	while (1)
	{
		printf("请输入扫描的行坐标（1~%d）、列坐标（1~%d）：", row - 2, col - 2);
		int input_num = scanf("%d %d", row_input, col_input);
		clean_cache(); // 清理缓存区
		if (*row_input < 1 || *row_input > row - 2 ||
			*col_input < 1 || *col_input > col - 2)
		{
			printf("输入错误，请重新输入！\n");
			continue;
		}
		else
			break; // 输入正确，退出循环
	}
}

void replace_shade_to_mine(char** board, char** shade_board)
{ // 玩家扫到雷后，将遮罩标记替换为雷标记
	int i, j;
	for (i = 1; i <= row - 2; ++i)
		for (j = 1; j <= col - 2; ++j)
			if (mine_flag == board[i][j])
				shade_board[i][j] = mine_flag; // 将遮罩标记替换为雷标记
}

int sign_mine(char** shade_board,
	int row_input, int col_input)
{ // 玩家自己标记扫到的雷的函数
	if (shade_flag == shade_board[row_input][col_input] &&
		self_flag != shade_board[row_input][col_input])
	{
		shade_board[row_input][col_input] = self_flag; // 标记雷 
		return 1; // 标记成功
	}
	else if (self_flag == shade_board[row_input][col_input])
	{
		printf("你已经标记过该位置！请重新输入！\n");
		return 2; // 标记过的位置
	}
	else
	{
		printf("输入的标记位置不正确或已经扫描，请重新输入！\n");
		return 0; // 标记失败
	}
}

void judge_sing(char** shade_board,
	int* row_input, int* col_input)
{	// 标记功能，添加标记会打印棋盘
start:
	printf("是否添加标记：Y/N？");
	char sign;
	int sign_input = scanf("%c", &sign);
	clean_cache(); // 清理缓存区
	//printf("%c\n", sign);
	if ('Y' != sign && 'y' != sign &&
		'N' != sign && 'n' != sign)
	{
		printf("输入错误，请重新输入！\n");
		goto start; // 输入错误，重新输入
	}
	else if ('N' == sign || 'n' == sign)
		goto end; // 跳出标记循环 return
	while ('Y' == sign || 'y' == sign)
	{ // 玩家选择添加标记
		printf("请输入需要标记的行坐标（1~%d）、列坐标（1~%d）：",
			row - 2, col - 2);
		int c = scanf("%d %d", row_input, col_input);
		clean_cache(); // 清理缓存区
		if (*row_input < 1 || *row_input > row - 2 ||
			*col_input < 1 || *col_input > col - 2)
		{
			printf("输入错误，请重新输入！\n");
			continue;
		}
		else
		{
			int judge = sign_mine(shade_board,
				*row_input, *col_input);
			{ // 玩家自己标记雷
				//printf("%d\n", judge);
				if (1 == judge)
				{
					printf("已添加标记，坐标%d行%d列\n",
						*row_input, *col_input);
					print_board(shade_board); // 打印棋盘
					printf("是否继续添加标记？Y/N？\n");
					break; // 标记成功，退出标记循环
				}
				else if (2 == judge)
				{
					char is_cancel, is_continue;
				resign:
					printf("该位置已经被标记，是否取消标记(Y/N)？");
					int is_cancel_input = scanf("%c", &is_cancel);
					clean_cache(); // 清理缓存区
					if ('Y' != is_cancel && 'y' != is_cancel &&
						'N' != is_cancel && 'n' != is_cancel)
					{
						printf("输入错误，请重新输入！\n");
						goto resign; // 输入错误，重新输入
					}
					else if ('Y' == is_cancel || 'y' == is_cancel)
					{
						shade_board[*row_input][*col_input] = shade_flag;
						printf("坐标%d行%d列已取消标记\n",
							*row_input, *col_input);
						print_board(shade_board); // 打印棋盘
					sure_again:
						printf("是否继续添加标记？Y/N？");
						int is_continue_input =
							scanf("%c", &is_continue);
						clean_cache(); // 清理缓存区
						if ('Y' != is_continue && 'y' != is_continue &&
							'N' != is_continue && 'n' != is_continue)
						{
							printf("输入错误，请重新输入！\n");
							goto sure_again; // 输入错误，重新输入
						}
						else if ('Y' == is_continue || 'y' ==
							is_continue)
						{
							print_board(shade_board);
							continue; // 继续标记
						}
						else
						{ // 退出标记循环
							printf("已经退出标记，游戏继续！\n");
							print_board(shade_board); // 打印棋盘
							goto end; // 退出标记循环 return
						}
					}
					else
						break; // 取消标记，退出标记循环
				}
				else
					continue; // 标记失败，继续标记
			}
		}
	}
	judge_sing(shade_board, row_input, col_input);
	// 判断是否继续标记
end:;
}

void sweep_game()
{ // 扫雷游戏主函数
	srand((unsigned int)time(NULL)); // 随机数种子
	int start_time = (unsigned int)time(NULL) * -1; // 记录游戏开始时间
	do
	{
		char** board = create_2d_array(row, col); // 扫雷游戏棋盘
		char** shade_board = create_2d_array(row, col); // 遮罩棋盘
		if (board != NULL)
		{
			init_board(board); // 初始化棋盘
			init_shade_board(shade_board); // 初始化遮罩棋盘
			print_board(shade_board); // 打印棋盘
			while (1)
			{
				int row_input, col_input; // 储存玩家输入的坐标
				player_game(&row_input, &col_input); // 玩家扫雷
				char x_y = shade_board[row_input][col_input]; // 玩家输入的位置
				int judge = chack_mine_count(board, shade_board,
					row_input, col_input); // 检查雷
				//printf("%c\n", x_y);
				if (shade_flag != x_y)
				{
					if (self_flag == x_y)
					{ // 玩家自己标记的雷位置
						printf("坐标%d行%d列已经被标记，确定要扫雷吗(Y/N)？",
							row_input, col_input);
						char is_sure;
						int is_sure_input = scanf("%c", &is_sure);
						clean_cache(); // 清理缓存区
						if ('Y' != is_sure && 'y' != is_sure &&
							'N' != is_sure && 'n' != is_sure)
						{
							printf("输入错误，请重新输入！\n");
							continue; // 输入错误，重新扫雷
						}
						if ('Y' == is_sure || 'y' == is_sure)
						{	// 确定要扫雷
							judge = chack_mine_count(board, shade_board,
								row_input, col_input); // 检查雷
							if (-1 == judge)
							{ // 玩家扫到雷，游戏结束
								print_board(shade_board); // 打印棋盘
								printf("你挖到雷了！游戏结束！所有雷标记如下：\n");
								replace_shade_to_mine(board, shade_board);
								print_board(shade_board); // 打印棋盘
								break;
							}
							else
								if (residue_shade_count(shade_board) <=
									mine_num)
								{ // 玩家扫完雷，游戏结束
									printf("恭喜成功排雷！游戏结束！\n");
									replace_shade_to_mine(board, shade_board);
									printf("所有雷标记如下：\n");
									print_board(shade_board); // 打印棋盘
									break;
								}
								else
								{ // 提示玩家是否标记雷，继续游戏
									if ('Y' == is_sign || 'y' == is_sign)
									{
										print_board(shade_board); // 打印棋盘
										judge_sing(shade_board,
											&row_input, &col_input);
										//print_board(shade_board); // 打印棋盘
									}
									else
										continue; // 玩家未扫完雷，继续游戏
								}
						}
						else
						{
							print_board(shade_board); // 打印棋盘
							goto again;
						}
					}
					printf("你已经扫了该位置！请重新输入！\n");
					print_board(shade_board); // 打印棋盘
				again:
					continue; // 玩家扫过该位置，继续游戏
				}
				if (-1 == judge)
				{ // 玩家扫到雷，游戏结束
					print_board(shade_board); // 打印棋盘
					printf("你挖到雷了！游戏结束！所有雷标记如下：\n");
					replace_shade_to_mine(board, shade_board);
					print_board(shade_board); // 打印棋盘
					break;
				}
				else
				{ // 玩家扫到非雷的位置
					//printf("%d\n", judge);
					print_board(shade_board); // 打印棋盘
					if (residue_shade_count(shade_board) <=
						mine_num)
					{ // 玩家扫完雷，游戏结束
						printf("恭喜成功排雷！游戏结束！\n");
						replace_shade_to_mine(board, shade_board);
						printf("所有雷标记如下：\n");
						print_board(shade_board); // 打印棋盘
						break;
					}
					else
					{ // 提示玩家是否标记雷，继续游戏
						if ('Y' == is_sign || 'y' == is_sign)
						{
							judge_sing(shade_board,
								&row_input, &col_input);
							//print_board(shade_board); // 打印棋盘
						}
						else
							continue; // 玩家未扫完雷，继续游戏
					}
				}
			}
			break;
		}
		else
			continue; // 内存分配失败，重新分配内存
	} while (1);
	int end_time = (unsigned int)time(NULL) * -1; // 记录游戏结束时间
	printf("游戏用时：%d秒\ns", start_time - end_time); // 打印游戏用时
}

void sweep_start()
{ // 扫雷游戏开始函数
	int choice = 0;
	//system("chcp 65001 > nul"); // 设定 cmd 窗口的字符集为 UTF-8
	do
	{
		sweep_menu();
		printf("请输入选择: ");
		int choice_input = scanf("%d", &choice);
		if (choice_input != 1)
		{
			printf("输入错误，请重新输入！\n");
			clean_cache(); // 清理缓存区
			continue;
		}
		switch (choice)
		{
		case 1: // 开始游戏
			printf("开始游戏！\n");
			is_sign = 'n'; // 玩家默认添加标记
			sweep_game();
			break;
		case 2: // 自定义游戏
			self_define();
			sweep_game();
			break;
		case 0: // 退出游戏
			return;
		default: // 输入错误
			printf("输入错误，请重新输入！\n");
			break;
		}
	} while (1);
}

//int main()
//{ // 主函数
//	sweep_start();
//	return 0;
//}